
#include <sstream>
#include "SQLDataTypeImpl.h"

#include "../../utils/FnvHash.h"
#include "../../utils/Types.h"
#include "expr/SQLIntegerExpr.h"
#include "../visitor/SQLASTVisitor.h"
#include "../../utils/listTran.h"

SQLDataTypeImpl::SQLDataTypeImpl()
{
  arguments = std::make_shared<std::list<SQLExpr_ptr>>();
}

SQLDataTypeImpl::~SQLDataTypeImpl()
{
}

SQLDataTypeImpl::SQLDataTypeImpl(string_ptr theName)
{
  arguments = std::make_shared<std::list<SQLExpr_ptr>>();
  this->name = theName;
}

SQLDataTypeImpl::SQLDataTypeImpl(string_ptr theName, int precision)
    : SQLDataTypeImpl(theName)
{
  addArgument(SQLIntegerExpr_ptr(new SQLIntegerExpr(precision)));
}

void SQLDataTypeImpl::accept0(SQLASTVisitor_ptr visitor)
{
  if (visitor->visit(SharedObject(SQLDataTypeImpl)))
  {
    SQLObject_list_ptr tmp = ListTran::tran_class<SQLExpr, SQLObject>(arguments);
    acceptChild(visitor, tmp);
  }

  visitor->endVisit(SharedObject(SQLDataTypeImpl));
}

string_ptr SQLDataTypeImpl::getName()
{
  return name;
}

void SQLDataTypeImpl::setName(string_ptr theName)
{
  name = theName;
}

SQLExpr_list_ptr SQLDataTypeImpl::getArguments()
{
  return arguments;
}

int SQLDataTypeImpl::hashCode()
{
  int prime = 31;
  int result = 1;
  result = prime * result + ((arguments->empty()) ? 0 : listHashcode());
  result = prime * result + ((*name == "") ? 0 : stringHashcode(name));
  return result;
}

int SQLDataTypeImpl::listHashcode()
{
  int c = 0;
  std::stringstream ss;
  ss << "x ";

  for (std::list<SQLExpr_ptr>::const_iterator ci = arguments->begin(); ci != arguments->end(); ++ci)
  {
    ss << "SQLExpr " << c++;
  }
  string_ptr x = make_string_ptr(ss.str());
  return stringHashcode(x);
}

int SQLDataTypeImpl::stringHashcode(string_ptr x)
{
  std::hash<std::string> string_hash;

  return string_hash(x->c_str());
}

bool SQLDataTypeImpl::equals(Object_ptr obj)
{

  // http://stackoverflow.com/questions/4364536/c-null-reference
  /*  A reference shall be initialized to refer to a valid object or function. [Note: in particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the “object” obtained by dereferencing a null pointer, which causes undefined behavior. As described in 9.6, a reference cannot be bound directly to a bit-field. ]  */

  // if (this == obj) return BOOL::TRUE;
  std::shared_ptr<SQLDataTypeImpl> x = std::dynamic_pointer_cast<SQLDataTypeImpl>(obj);
  if (this == x.get())
    return true;
  if (x == NULL)
    return false;

  if (getClass() != x->getClass())
    return false;
  // SQLDataTypeImpl other = (SQLDataTypeImpl) obj;
  if (arguments->empty())
  {
    if (!(x->getArguments())->empty())
      return false;
  }
  // else if (!arguments.equals(other.arguments)) return BOOL::FALSE;
  if (*name == "")
  {
    if (*x->getName() != "")
      return false;
  }
  else if (name != x->getName())
    return false;

  return true;
}

BOOL_ptr SQLDataTypeImpl::hasKeyLength()
{
  long hashCode64 = nameHashCode64();

  if (hashCode64 == FnvHash::Constants::VARCHAR ||
      hashCode64 == FnvHash::Constants::VARCHAR2 ||
      hashCode64 == FnvHash::Constants::CHAR ||
      hashCode64 == FnvHash::Constants::NCHAR ||
      hashCode64 == FnvHash::Constants::NVARCHAR ||
      hashCode64 == FnvHash::Constants::NVARCHAR2 ||
      hashCode64 == FnvHash::Constants::TEXT ||
      hashCode64 == FnvHash::Constants::TINYTEXT ||
      hashCode64 == FnvHash::Constants::MEDIUMTEXT ||
      hashCode64 == FnvHash::Constants::LONGTEXT ||
      hashCode64 == FnvHash::Constants::CLOB ||
      hashCode64 == FnvHash::Constants::NCLOB ||
      hashCode64 == FnvHash::Constants::MULTIVALUE ||
      hashCode64 == FnvHash::Constants::STRING ||
      hashCode64 == FnvHash::Constants::BLOB ||
      hashCode64 == FnvHash::Constants::TINYBLOB ||
      hashCode64 == FnvHash::Constants::LONGBLOB ||
      hashCode64 == FnvHash::Constants::BINARY ||
      hashCode64 == FnvHash::Constants::VARBINARY)
  {
    return BOOL::TRUE;
  }
  else
  {
    BOOL::FALSE;
  }
}

long SQLDataTypeImpl::nameHashCode64()
{
  if (nameHashCode64_ == 0)
  {
    nameHashCode64_ = FnvHash::hashCode64(name);
  }
  return nameHashCode64_;
}

SQLObject_ptr SQLDataTypeImpl::clone()
{
  SQLObject_ptr clone_tmp = std::shared_ptr<SQLDataTypeImpl>(new SQLDataTypeImpl());
  std::shared_ptr<SQLDataTypeImpl> x = std::dynamic_pointer_cast<SQLDataTypeImpl>(clone_tmp);
  // SQLDataTypeImpl *x = new SQLDataTypeImpl();

  cloneTo(x);

  return x;
}

int SQLDataTypeImpl::jdbcType()
{
  long nameNash = nameHashCode64();

  if (nameNash == FnvHash::Constants::TINYINT)
  {
    return Types::TINYINT;
  }

  if (nameNash == FnvHash::Constants::SMALLINT)
  {
    return Types::SMALLINT;
  }

  if (nameNash == FnvHash::Constants::INT ||
      nameNash == FnvHash::Constants::INTEGER)
  {
    return Types::INTEGER;
  }

  if (nameNash == FnvHash::Constants::BIGINT)
  {
    return Types::BIGINT;
  }

  if (nameNash == FnvHash::Constants::DECIMAL)
  {
    return Types::DECIMAL;
  }

  if (nameNash == FnvHash::Constants::FLOAT)
  {
    return Types::FLOAT;
  }

  if (nameNash == FnvHash::Constants::REAL)
  {
    return Types::REAL;
  }

  if (nameNash == FnvHash::Constants::DOUBLE)
  {
    return Types::DOUBLE;
  }

  if (nameNash == FnvHash::Constants::NUMBER ||
      nameNash == FnvHash::Constants::NUMERIC)
  {
    return Types::NUMERIC;
  }

  if (nameNash == FnvHash::Constants::BOOLEAN)
  {
    return Types::BOOLEAN;
  }

  if (nameNash == FnvHash::Constants::DATE ||
      nameNash == FnvHash::Constants::NEWDATE)
  {
    return Types::DATE;
  }

  if (nameNash == FnvHash::Constants::DATETIME ||
      nameNash == FnvHash::Constants::TIMESTAMP)
  {
    return Types::TIMESTAMP;
  }

  if (nameNash == FnvHash::Constants::TIME)
  {
    return Types::TIME;
  }

  if (nameNash == FnvHash::Constants::BLOB)
  {
    return Types::BLOB;
  }

  if (nameNash == FnvHash::Constants::ROWID)
  {
    return Types::ROWID;
  }

  if (nameNash == FnvHash::Constants::REF)
  {
    return Types::REF;
  }

  if (nameNash == FnvHash::Constants::TINYINT ||
      nameNash == FnvHash::Constants::TINY)
  {
    return Types::TINYINT;
  }

  if (nameNash == FnvHash::Constants::SMALLINT ||
      nameNash == FnvHash::Constants::SHORT)
  {
    return Types::SMALLINT;
  }

  if (nameNash == FnvHash::Constants::INT ||
      nameNash == FnvHash::Constants::INT24 ||
      nameNash == FnvHash::Constants::INTEGER)
  {
    return Types::INTEGER;
  }

  if (nameNash == FnvHash::Constants::NUMBER ||
      nameNash == FnvHash::Constants::NUMERIC)
  {
    return Types::NUMERIC;
  }

  if (nameNash == FnvHash::Constants::BOOLEAN)
  {
    return Types::BOOLEAN;
  }

  if (nameNash == FnvHash::Constants::DATE ||
      nameNash == FnvHash::Constants::YEAR ||
      nameNash == FnvHash::Constants::NEWDATE)
  {
    return Types::DATE;
  }

  if (nameNash == FnvHash::Constants::DATETIME ||
      nameNash == FnvHash::Constants::TIMESTAMP)
  {
    return Types::TIMESTAMP;
  }

  if (nameNash == FnvHash::Constants::TIME)
  {
    return Types::TIME;
  }

  if (nameNash == FnvHash::Constants::TINYBLOB)
  {
    return Types::VARBINARY;
  }

  if (nameNash == FnvHash::Constants::BLOB)
  {
    return Types::BLOB;
  }

  if (nameNash == FnvHash::Constants::LONGBLOB)
  {
    return Types::LONGVARBINARY;
  }

  if (nameNash == FnvHash::Constants::ROWID)
  {
    return Types::ROWID;
  }

  if (nameNash == FnvHash::Constants::REF)
  {
    return Types::REF;
  }

  if (nameNash == FnvHash::Constants::BINARY ||
      nameNash == FnvHash::Constants::GEOMETRY)
  {
    return Types::BINARY;
  }

  if (nameNash == FnvHash::Constants::SQLXML)
  {
    return Types::SQLXML;
  }

  if (nameNash == FnvHash::Constants::BIT)
  {
    return Types::BIT;
  }

  if (nameNash == FnvHash::Constants::NCHAR)
  {
    return Types::NCHAR;
  }

  if (nameNash == FnvHash::Constants::CHAR ||
      nameNash == FnvHash::Constants::ENUM ||
      nameNash == FnvHash::Constants::SET ||
      nameNash == FnvHash::Constants::JSON)
  {
    return Types::CHAR;
  }

  if (nameNash == FnvHash::Constants::VARCHAR ||
      nameNash == FnvHash::Constants::VARCHAR2 ||
      nameNash == FnvHash::Constants::STRING)
  {
    return Types::VARCHAR;
  }

  if (nameNash == FnvHash::Constants::NVARCHAR ||
      nameNash == FnvHash::Constants::NVARCHAR2)
  {
    return Types::NVARCHAR;
  }

  if (nameNash == FnvHash::Constants::CLOB ||
      nameNash == FnvHash::Constants::TEXT ||
      nameNash == FnvHash::Constants::TINYTEXT ||
      nameNash == FnvHash::Constants::MEDIUMTEXT ||
      nameNash == FnvHash::Constants::LONGTEXT)
  {
    return Types::CLOB;
  }

  if (nameNash == FnvHash::Constants::NCLOB)
  {
    return Types::NCLOB;
  }

  if (nameNash == FnvHash::Constants::TINYBLOB)
  {
    return Types::VARBINARY;
  }

  if (nameNash == FnvHash::Constants::LONGBLOB)
  {
    return Types::LONGVARBINARY;
  }

  if (nameNash == FnvHash::Constants::BINARY ||
      nameNash == FnvHash::Constants::GEOMETRY)
  {
    return Types::BINARY;
  }

  if (nameNash == FnvHash::Constants::SQLXML)
  {
    return Types::SQLXML;
  }

  //

  if (nameNash == FnvHash::Constants::NCHAR)
  {
    return Types::NCHAR;
  }

  if (nameNash == FnvHash::Constants::CHAR ||
      nameNash == FnvHash::Constants::JSON)
  {
    return Types::CHAR;
  }

  if (nameNash == FnvHash::Constants::VARCHAR ||
      nameNash == FnvHash::Constants::VARCHAR2 ||
      nameNash == FnvHash::Constants::STRING)
  {
    return Types::VARCHAR;
  }

  if (nameNash == FnvHash::Constants::NVARCHAR ||
      nameNash == FnvHash::Constants::NVARCHAR2)
  {
    return Types::NVARCHAR;
  }

  if (nameNash == FnvHash::Constants::CLOB ||
      nameNash == FnvHash::Constants::TEXT ||
      nameNash == FnvHash::Constants::TINYTEXT ||
      nameNash == FnvHash::Constants::MEDIUMTEXT ||
      nameNash == FnvHash::Constants::LONGTEXT)
  {
    return Types::CLOB;
  }

  if (nameNash == FnvHash::Constants::NCLOB)
  {
    return Types::NCLOB;
  }

  return 0;
}

void SQLDataTypeImpl::cloneTo(SQLDataTypeImpl_ptr x)
{
  x->dbType = dbType;
  x->name = name;
  x->nameHashCode64_ = nameHashCode64_;

  // for (SQLExpr_ptr arg : arguments) {
  //     x->addArgument(arg->clone());
  // }

  // x->withTimeZone = withTimeZone;
  // x->withLocalTimeZone = withLocalTimeZone;
  // x->zerofill = zerofill;
  // x->unsigned = unsigned;

  // if (indexBy != null) {
  //     x->setIndexBy(indexBy->clone());
  // }
}

void SQLDataTypeImpl::addArgument(SQLExpr_ptr argument)
{
  if (argument != NULL)
  {
    argument->setParent(SharedObject(SQLDataTypeImpl));
  }
  this->arguments->push_back(argument);
}

void SQLDataTypeImpl::setUnsigned(BOOL_ptr unsigned_)
{
  this->unsigned_ = unsigned_;
}

void SQLDataTypeImpl::setZerofill(BOOL_ptr zerofill)
{
  this->zerofill = zerofill;
}

void SQLDataTypeImpl::setIndexBy(SQLExpr_ptr x)
{
  if (x != nullptr)
  {
    x->setParent(SharedObject(SQLDataTypeImpl));
  }
  this->indexBy = x;
}

SQLExpr_ptr SQLDataTypeImpl::getIndexBy()
{
  return indexBy;
}
